﻿// InstructionFactory.cs
// contains the InstructionFactory class

namespace armsim
{
    /// <summary>
    /// Generates a given instruction instance depending on the given instruction
    /// </summary>
    public class InstructionFactory
    {
        // ------------------------------------------------------------------------------
        // General Section
        // ------------------------------------------------------------------------------

        /// <summary>
        /// Generate an Instruction based on the instruction given to it
        /// </summary>
        /// <param name="instruction">the 32bit instruction</param>
        /// <returns>Instruction object with the correct generated class</returns>
        public static BaseInstruction GenerateInstruction(uint instruction)
        {

            if (instruction == 0)
            {
                return new BaseInstruction(instruction);
            }

            // Check special cases first
       
            // MUL
            if (VirtualMemory.ExtractBits(instruction, 24, 27) == 0x0u && VirtualMemory.ExtractBits(instruction, 4, 7) == 0x9u && VirtualMemory.ExtractBits(instruction, 28, 31) != 0xFu)
            {
                return new MulInstruction(instruction);
            }

            // Check the instruction type

            var type = VirtualMemory.ExtractBits(instruction, 25, 27);
            var bit24 = VirtualMemory.ExtractBits(instruction, 24, 24);

            // Data Processing)
            if (type == 0u || type == 1u)
            {
                return GenerateDataProcessingInstruction(instruction);
            }

            // Load/Store
            if (type == 2u || type == 3u)
            {
                return GenerateLoadStoreInstruction(instruction);
            }

            // Load/Store Multiple
            if (type == 4u)
            {

                return GenerateLoadStoreMultipleInstruction(instruction);
            }

            // Branch
            if (type == 5u)
            {
                return GenerateBranchInstruction(instruction);
            }

            // SWI
            if (type == 7u && bit24 == 1u)
            {

                return GenerateSwiInstruction(instruction);
            }

            return new BaseInstruction(instruction);
        }

        /// <summary>
        /// Generates SWIInstruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>an SWIInstruction object</returns>
        private static BaseInstruction GenerateSwiInstruction(uint instruction)
        {
            return new SwiInstruction(instruction);
        }

        /// <summary>
        /// Generates Load Store Multiple Instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>an LoadStoreMultipleInstruction object</returns>
        private static BaseInstruction GenerateLoadStoreMultipleInstruction(uint instruction)
        {
            var bit20 = VirtualMemory.ExtractBits(instruction, 20, 20);

            switch (bit20)
            {
                // LDM
                case 1u:
                    return GenerateLdmInstruction(instruction);

                // STM
                case 0u:
                    return GenerateStmInstruction(instruction);

                default:
                    return GenerateInstruction(instruction);
            }
        }

        /// <summary>
        /// Generate a Branch instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static BaseInstruction GenerateBranchInstruction(uint instruction)
        {
            return new BaseInstruction(instruction);
        }

        // ------------------------------------------------------------------------------
        // Load Store Section
        // ------------------------------------------------------------------------------

        /// <summary>
        /// Generate a Load or Store instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static LoadStoreInstruction GenerateLoadStoreInstruction(uint instruction)
        {
            var type = VirtualMemory.ExtractBits(instruction, 25, 27);
            var bit22 = VirtualMemory.ExtractBits(instruction, 22, 22);
            var bit20 = VirtualMemory.ExtractBits(instruction, 20, 20);

            var opCode = VirtualMemory.ExtractBits(instruction, 21, 24);

            // stm
            //if (type == 4u && bit22 == 0u && bit20 == 0u)
            //{
            //    return GenerateStmInstruction(instruction);
            //}

            // ldm
            //if (type == 4u && bit22 == 0u && bit20 == 1u)
            //{
            //    return GenerateLdmInstruction(instruction);
            //}

            // ldr
            if (bit22 == 0u && bit20 == 1u)
            {
                return GenerateLdrInstruction(instruction);
            }

            // str
            if (bit22 == 0u && bit20 == 0u)
            {
                return GenerateStrInstruction(instruction);
            }

            // ldrb
            if (bit22 == 1u && bit20 == 1u)
            {
                return GenerateLdrbInstruction(instruction);
            }

            // strb
            if (bit22 == 1u && bit20 == 0u)
            {
                return GenerateStrbInstruction(instruction);
            }

            return new LoadStoreInstruction(instruction);


            /*
            switch (opCode)
            {
                // ldr
                case 1:
                    return null;

                // str
                case 2:
                    return null;

                // stm
                case 3:
                    return null;

                // ldm
                case 4:
                    return null;

                default:
                    return null;
            }
            */
        }

        /// <summary>
        /// Generate a ldrb instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static LoadStoreInstruction GenerateLdrbInstruction(uint instruction)
        {
            return new LdrbInstruction(instruction);
        }

        /// <summary>
        /// Generate a strb instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static LoadStoreInstruction GenerateStrbInstruction(uint instruction)
        {
            return new StrbInstruction(instruction);
        }

        /// <summary>
        /// Generate a str instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static LoadStoreInstruction GenerateStrInstruction(uint instruction)
        {
            return new StrInstruction(instruction);
        }

        /// <summary>
        /// Generate a ldr instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static LoadStoreInstruction GenerateLdrInstruction(uint instruction)
        {
            return new LdrInstruction(instruction);
        }

        /// <summary>
        /// Generate a ldm instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static LoadStoreMultipleInstruction GenerateLdmInstruction(uint instruction)
        {
            return new LdmInstruction(instruction);
        }

        /// <summary>
        /// Generate a stm instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static LoadStoreMultipleInstruction GenerateStmInstruction(uint instruction)
        {
            return new StmInstruction(instruction);
        }

        // ------------------------------------------------------------------------------
        // Data Processing Section
        // ------------------------------------------------------------------------------

        /// <summary>
        /// Generate a data processing instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static DataProcessingInstruction GenerateDataProcessingInstruction(uint instruction)
        {
            var opCode = VirtualMemory.ExtractBits(instruction, 21, 24);

            switch (opCode)
            {
                // mov
                case 13u:
                    return GenerateMovInstruction(instruction);

                // mvn
                case 15u:
                    return GenerateMvnInstruction(instruction);

                // add
                case 4u:
                    return GenerateAddInstruction(instruction);

                // sub
                case 2u:
                    return GenerateSubInstruction(instruction);

                // rsb
                case 3u:
                    return GenerateRsbInstruction(instruction);

                // and
                case 0u:
                    return GenerateAndInstruction(instruction);

                // orr
                case 12u:
                    return GenerateOrrInstruction(instruction);

                // eor
                case 1u:
                    return GenerateEorInstruction(instruction);

                // bic
                case 14u:
                    return GenerateBicInstruction(instruction);

                // Default
                default:
                    return new DataProcessingInstruction(instruction);
            }


        }

        /// <summary>
        /// Generate a add instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static DataProcessingInstruction GenerateAddInstruction(uint instruction)
        {
            return new AddInstruction(instruction);
        }

        /// <summary>
        /// Generate a sub instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static DataProcessingInstruction GenerateSubInstruction(uint instruction)
        {
            return new SubInstruction(instruction);
        }

        /// <summary>
        /// Generate a bic instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static DataProcessingInstruction GenerateBicInstruction(uint instruction)
        {
            return new BicInstruction(instruction);
        }

        /// <summary>
        /// Generate a eor instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static DataProcessingInstruction GenerateEorInstruction(uint instruction)
        {
            return new EorInstruction(instruction);
        }

        /// <summary>
        /// Generate a zzzz instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static DataProcessingInstruction GenerateOrrInstruction(uint instruction)
        {
            return new OrrInstruction(instruction); ;
        }

        /// <summary>
        /// Generate a and instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static DataProcessingInstruction GenerateAndInstruction(uint instruction)
        {
            return new AndInstruction(instruction);
        }

        /// <summary>
        /// Generate a rsb instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static DataProcessingInstruction GenerateRsbInstruction(uint instruction)
        {
            return new RsbInstruction(instruction);
        }

        /// <summary>
        /// Generate a mvn instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static DataProcessingInstruction GenerateMvnInstruction(uint instruction)
        {
            return new MvnInstruction(instruction);
            
        }

        /// <summary>
        /// Generate a mov instruction
        /// </summary>
        /// <param name="instruction">32 bit instruction to be read from</param>
        /// <returns>the initialized instruction</returns>
        private static DataProcessingInstruction GenerateMovInstruction(uint instruction)
        {
            return new MovInstruction(instruction);
            
        }

    }

}
